using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;

namespace Quiki.ExpressionParsers
{
    public class WikiTextElement
    {
        public WikiTextElement(WikiTextElementType elementType, int level)
        {
            this.elementType = elementType;
            this.level = level;
        }

        public WikiTextElement (WikiTextElementType elementType, int? level, string text)
        {
            this.elementType = elementType;
            this.level = level;
            this.text = text;
        }

        public WikiTextElement (WikiTextElementType elementType, string text)
        {
            this.elementType = elementType;
            this.text = text;
        }

        public WikiTextElement (WikiTextElementType elementType, WikiTextElement childElement)
        {
            this.elementType = elementType;
            childElements.Add(childElement);
        }

        public WikiTextElement(WikiTextElementType elementType)
        {
            this.elementType = elementType;
        }

        public WikiTextElement(string text)
        {
            elementType = WikiTextElementType.Text;
            this.text = text;
        }

        public IList<WikiTextElement> ChildElements
        {
            get { return childElements; }
        }

        public WikiTextElementType ElementType
        {
            get { return elementType; }
            set { elementType = value; }
        }

        public int? Level
        {
            get { return level; }
            set { level = value; }
        }

        public IDictionary<string, object> Properties
        {
            get { return properties; }
        }

        public bool Terminal
        {
            get { return terminal; }
            set { terminal = value; }
        }

        public string Text
        {
            get { return text; }
            set { text = value; }
        }

        public int CountElements (Func<WikiTextElement, bool> predicate)
        {
            int i = 0;
            if (predicate (this))
                i++;

            foreach (WikiTextElement childElement in childElements)
            {
                int childCount = childElement.CountElements(predicate);
                i += childCount;
            }

            return i;
        }

        [SuppressMessage ("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "depth+1")]
        public void Dump (TextWriter writer, int depth)
        {
            StringBuilder value = new StringBuilder();
            value.AppendFormat("{0}{1} : text='{2}'", new string ('\t', depth), elementType, text);

            foreach (KeyValuePair<string, object> property in properties)
                value.AppendFormat(" {0}={1}", property.Key, property.Value);

            writer.WriteLine(value);

            foreach (WikiTextElement childElement in childElements)
                childElement.Dump (writer, depth + 1);
        }

        public WikiTextElement FindElement (Func<WikiTextElement, bool> predicate)
        {
            if (predicate(this))
                return this;

            foreach (WikiTextElement childElement in childElements)
            {
                WikiTextElement foundElement = childElement.FindElement(predicate);
                if (foundElement != null)
                    return foundElement;
            }

            return null;
        }

        public void ForEachElement (Func<WikiTextElement, bool> predicate, Action<WikiTextElement> action)
        {
            if (predicate (this))
                action(this);

            foreach (WikiTextElement childElement in childElements)
                childElement.ForEachElement(predicate, action);
        }

        public bool Remove (WikiTextElement element)
        {
            if (ReferenceEquals (this, element))
                throw new InvalidOperationException ();

            for (int i = 0; i < childElements.Count; i++)
            {
                WikiTextElement childElement = childElements[i];
                if (ReferenceEquals (childElement, element))
                {
                    childElements.RemoveAt(i);
                    return true;
                }
            }

            foreach (WikiTextElement childElement in childElements)
            {
                if (childElement.Remove(element))
                    return true;
            }

            return false;
        }

        public bool Replace (WikiTextElement element, WikiTextElement withElement)
        {
            if (ReferenceEquals(this, element))
                throw new InvalidOperationException();

            for (int i = 0; i < childElements.Count; i++)
            {
                WikiTextElement childElement = childElements[i];
                if (ReferenceEquals(childElement, element))
                {
                    childElements[i] = withElement;
                    return true;
                }
            }

            foreach (WikiTextElement childElement in childElements)
            {
                if (childElement.Replace(element, withElement))
                    return true;
            }

            return false;
        }

        public override string ToString ()
        {
            using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                Dump(writer, 0);
                return writer.ToString();
            }
        }

        private bool terminal;
        private WikiTextElementType elementType;
        private int? level;
        private readonly List<WikiTextElement> childElements = new List<WikiTextElement>();
        private string text;
        private readonly Dictionary<string, object> properties = new Dictionary<string, object>();
    }
}